/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) RMB.java 2018-08-05 23:52
 */

package cn.jh.common.core.base;

import org.jetbrains.annotations.Contract;

import java.math.BigDecimal;

import static com.google.common.base.Preconditions.checkArgument;
import static java.math.BigDecimal.ROUND_HALF_EVEN;
import static java.math.BigDecimal.ZERO;


/**
 * 人民币大写转换工具类。
 * <pre>{@code
 * RMB.toUpperCase(new BigDecimal("1020.05")) = "壹仟零贰拾圆零伍分"
 * RMB.toUpperCase(new BigDecimal("1000")) = "壹仟圆整"
 * }</pre>
 *
 * @author Fuchun
 * @since 1.0
 */
public abstract class RMB {

    // "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"
    private static final String[] RMB_UPPERS = new String[]{
            "\u96f6", "\u58f9", "\u8d30", "\u53c1", "\u8086", "\u4f0d", "\u9646", "\u67d2", "\u634c", "\u7396"
    };
    // 个、拾、佰、仟、万、亿、兆(12)、京(16)、垓(20)、秭(24)、穰(28)、沟(32)、涧(36)、正(40)、载(44)
    //
    // "圆", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "万", "拾万", "佰万", "仟万", "万万"
    private static final String[] RMB_UNITS = new String[]{
            "\u5706", "\u62fe", "\u4f70", "\u4edf", "\u4e07", "\u62fe", "\u4f70", "\u4edf", "\u4ebf",
            "\u62fe", "\u4f70", "\u4edf", "\u4e07", "\u62fe\u4e07", "\u4f70\u4e07", "\u4edf\u4e07", "\u4e07\u4e07"
    };

    private static final String RMB_ZERO = "\u96f6"; // 零
    private static final char UNIT_YUAN = '\u5706'; // 圆
    private static final char UNIT_CENT = '\u5206'; // 分
    private static final char UNIT_TEN_CENT = '\u89d2'; // 角
    private static final String UNIT_YUAN_STR = Character.toString(UNIT_YUAN);
    private static final String UNIT_CENT_STR = Character.toString(UNIT_CENT);
    private static final String UNIT_TEN_CENT_STR = Character.toString(UNIT_TEN_CENT);

    // "零仟", "零佰", "零拾"
    private static final String[] RULE_1 = {"\u96f6\u4edf", "\u96f6\u4f70", "\u96f6\u62fe"};
    // "零亿", "零万", "零圆"
    private static final String[] RULE_2 = {"\u96f6\u4ebf", "\u96f6\u4e07", "\u96f6\u5706"};
    // "亿", "万", "圆"
    private static final String[] RULE_3 = {"\u4ebf", "\u4e07", "\u5706"};
    // 零零零
    private static final String ZERO_3 = "\u96f6\u96f6\u96f6";
    private static final String ZERO_2 = "\u96f6\u96f6"; // 零零

    private static final String RMB_ZERO_WITH_UNIT = RMB_UPPERS[0] + RMB_UNITS[0];
    /** 大写人民币没有分和角时，末尾必须跟 “整” */
    private static final String SUFFIX = "\u6574";

    /**
     * 将指定的人民币数额转换为中文人民币大写字符串。
     *
     * @param money 要转换的人民币数额。
     * @return 返回人民币大写格式字符串。
     * @throws IllegalArgumentException 如果指定的{@code money == null || money < 0}
     */
    @Contract("null -> fail")
    @SuppressWarnings("StringSplitter")
    public static String toUpperCase(BigDecimal money) {
        checkArgument(money != null && money.compareTo(ZERO) >= 0,
                "The money must be not null and greater than 0");
        if (money.compareTo(ZERO) == 0) {
            return RMB_ZERO_WITH_UNIT;
        }

        BigDecimal input = money.setScale(2, ROUND_HALF_EVEN);
        String string = input.toPlainString();
        String[] rn = string.split("\\.");  // assert rn.length == 2
        int[] intParts = toNumberParts(rn[0]), decimalParts = toNumberParts(rn[1]);

        StringBuilder builder = handleIntPart(intParts);
        // 零圆 replace to 圆
        String decimalString = parseDecimalString(decimalParts);
        builder.append(decimalString);
        return builder.toString();
    }

    static StringBuilder handleIntPart(int[] intParts) {
        StringBuilder builder = new StringBuilder();
        int i = 0, ipLen = intParts.length;
        for (int ip : intParts) {
            builder.append(RMB_UPPERS[ip]).append(RMB_UNITS[intParts.length - i - 1]);
            i++;
        }

        int pos;
        for (String r : RULE_1) {
            pos = builder.indexOf(r);
            while (pos >= 0) {
                builder.replace(pos, pos + r.length(), RMB_ZERO);
                pos = builder.indexOf(r);
            }
        }
        for (i = 0; i < 3; i++) {
            replaceBuilder(builder, ZERO_3, RMB_ZERO);
            replaceBuilder(builder, ZERO_2, RMB_ZERO);
        }
        if (ipLen >= 5 && ipLen <= 8) {
            replaceBuilder(builder, RULE_2[0], "");
            replaceBuilder(builder, RULE_2[1], RULE_3[1]);
        } else if (ipLen > 8 && ipLen <= 12) {
            replaceBuilder(builder, RULE_2[0], RULE_3[0]);
            if (builder.indexOf("\u4ebf\u96f6\u4e07") != -1) { // 亿零万
                replaceBuilder(builder, RULE_2[1], "");
            } else {
                replaceBuilder(builder, RULE_2[1], RULE_3[1]);
            }
        } else {
            replaceBuilder(builder, RULE_2[0], RULE_3[0]);
            if (builder.indexOf("\u4ebf\u96f6\u4e07") != -1) { // 亿零万
                replaceBuilder(builder, RULE_2[1], "", true);
            } else {
                replaceBuilder(builder, RULE_2[1], RULE_3[1]);
            }
        }
        // 零圆 replace to 圆
        replaceBuilder(builder, RULE_2[2], RULE_3[2]);
        return builder;
    }

    static String parseDecimalString(int[] decimalParts) {
        int tenCents = decimalParts[0], cent = decimalParts[1];
        if (tenCents == 0 && cent == 0) {
            return SUFFIX;
        }
        StringBuilder builder;
        if (tenCents == 0) { // 0 角
            builder = new StringBuilder(RMB_ZERO);
        } else {  // n 角
            builder = new StringBuilder();
            builder.append(RMB_UPPERS[tenCents]).append(UNIT_TEN_CENT);
        }
        if (cent != 0) {
            builder.append(RMB_UPPERS[cent]).append(UNIT_CENT);
        }
        return builder.toString();
    }

    private static void replaceBuilder(
            final StringBuilder builder, String source, String replacement) {
        replaceBuilder(builder, source, replacement, false);
    }

    private static void replaceBuilder(
            final StringBuilder builder, String source, String replacement, boolean recursion) {
        int pos = builder.indexOf(source);
        while (pos >= 0) {
            builder.replace(pos, pos + source.length(), replacement);
            if (recursion) {
                pos = builder.indexOf(source);
            } else {
                break;
            }
        }
    }

    @SuppressWarnings("StringSplitter")
    static int[] toNumberParts(String rmbString) {
        String[] parts = rmbString.split("");
        int[] result = new int[parts.length];
        int i = 0;
        for (String part : parts) {
            result[i] = Integer.parseInt(part);
            i++;
        }
        return result;
    }

    private RMB() {
    }
}
